﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;

using Effects.Wave;
using Effects.Effects.GuiParts;

namespace Effects.Effects
{
    /// <summary>
    ///  Pitchshifter 
    /// </summary>
    public class PitchShift : Effect
    {  
        #region controls
        // user controlled components

        #region footswitch

        /// <summary>
        ///  Footswitch for by passing effect.
        /// </summary>
        private Footswitch footswitch;

        /// <summary>
        ///  Led indicating On/Off state.
        /// </summary>
        private LED led;

        /// <summary>
        ///  Synchornizer for footswitch and led.
        /// </summary>
        private FootswitchLedSynchronizer synchronizer;


        /// <summary>
        ///  Key for footswitch settings.
        /// </summary>
        private int footswitchKey = 0;

        #endregion
        
        #region pitchshift
        /// <summary>
        ///  Potentiometer controlling the pitshift parameter.
        /// </summary>
        protected Pot pitchShiftPot;

        /// <summary>
        ///  Current pitchshift parameter.
        /// </summary>
        protected double pitchShift;

        /// <summary>
        ///  Key for the pitchshift parameter.
        /// </summary>
        protected const int pitchShiftKey = 1;

        #endregion

        #region depth
        /// <summary>
        ///  Potentiometer controlling depth parameter (dry/wet mix)
        /// </summary>
        protected Pot depthPot;

        /// <summary>
        ///  Current dry/wet mix.
        /// </summary>
        protected double depth;

        /// <summary>
        ///  Key for the depth parameter.
        /// </summary>
        protected const int depthKey = 2;

        #endregion

        #endregion

        #region algorithm_fields
        // necessary fields for algorithm

        /// <summary>
        ///  overlap factor (1-32)
        /// </summary>
        protected int overlap;

        /// <summary>
        ///  Size of one step = dataSize / overlap
        /// </summary>
        private int stepSize;

        /// <summary>
        ///  Provides Fourier transform.
        /// </summary>
        private FFT fft;

        // overriding overlap factor property
        /// <summary>
        /// Gets or sets current overlap
        /// </summary>
        public int Overlap
        {
            get
            {
                return overlap;
            }
            set
            {
                overlap = value;
                stepSize = dataSize / overlap;
            }
        }

        // overriding data size property
        /// <summary>
        ///  Gets or sets the data size of input parts.
        /// </summary>
        public override int DataSize
        {
            get
            {
                return base.DataSize;
            }
            set
            {
                dataSize = value;
                fft = new FFT(dataSize);
                lastPhase = new double[2 * dataSize];
                analysisMagn = new double[2 * dataSize];
                analysisFreq = new double[2 * dataSize];

                synthesisMagn = new double[2 * dataSize];
                synthesisFreq = new double[2 * dataSize];

                sumPhase = new double[2 * dataSize];
                buffer = new double[dataSize];
                outBuffer = new double[dataSize];
                stepSize = dataSize / overlap;
            }
        }

        /// <summary>
        ///  Gets the name of the Pitchshifter.
        /// </summary>
        public override string Name
        {
            get { return "FFT Pitchshifter v1.0"; }
        }

        /// <summary>
        ///  Phases from last processing
        /// </summary>
        private double[] lastPhase;

        /// <summary>
        ///  Input magnitudes of frequencies.
        /// </summary>
        private double[] analysisMagn;

        /// <summary>
        ///  Input frequencies
        /// </summary>
        private double[] analysisFreq;

        /// <summary>
        ///  Ouput magnitudes of frequencies
        /// </summary>
        private double[] synthesisMagn;

        /// <summary>
        ///  Output frequencies
        /// </summary>
        private double[] synthesisFreq;

        /// <summary>
        ///  Suming pahses for output
        /// </summary>
        private double[] sumPhase;

        /// <summary>
        ///  Input buffer
        /// </summary>
        private double[] buffer;

        /// <summary>
        ///  Output buffer
        /// </summary>
        private double[] outBuffer;

        #endregion

        #region constructor
        // constructor and initializing components

        /// <summary>
        ///  Creates new Pitchshifter
        /// </summary>
        public PitchShift()
        {
            overlap = 4;
            //this.BackgroundImage = System.Drawing.Image.FromStream(new System.IO.MemoryStream(Resources.PitchShift));
            this.BackgroundImage = System.Drawing.Image.FromStream(new System.IO.MemoryStream(global::Effects.Recources.EffectBackgorunds.Pitchshifter));
            this.Size = this.BackgroundImage.Size;
            InitializeComponent();
            InitializeFootswitch();
        }

        /// <summary>
        ///  Initializes potentiometers and overlapForm.
        /// </summary>
        private void InitializeComponent()
        {
            // pitchshift pot
            pitchShiftPot = new Pot();
            pitchShiftPot.MinValue = 0.5;
            pitchShiftPot.MaxValue = 2;
            pitchShiftPot.Knob = PotKnob.ChickenKnobMiddle;
            pitchShiftPot.Location = new System.Drawing.Point(15, 43);
            pitchShiftPot.ValueChanged += new PotValueChangedEventHandler(this.PitchshiftPotMoved);

            // depth pot
            depthPot=new Pot();
            depthPot.MaxValue=1;
            depthPot.MinValue=0;
            depthPot.Location = new System.Drawing.Point(110, 43);
            depthPot.Knob = PotKnob.ChickenKnobMiddle;
            //depthPot.Size = new System.Drawing.Size(110, 110);
            depthPot.ValueChanged+=new PotValueChangedEventHandler(this.DepthPotMoved);

            this.Controls.Add(this.pitchShiftPot);
            this.Controls.Add(depthPot);
            pitchShiftPot.Reset();
            depthPot.Reset();

            // overlap form
            overlapForm = new OverlapFactorForm();
            overlapForm.OnOverlapChange += new NewOverlapFactor(this.OverlapChanged);
            overlapForm.SetOverlap(this.overlap);
        }

        /// <summary>
        ///  Iniializes footswitch and led.
        /// </summary>
        private void InitializeFootswitch()
        {
            // footswitch
            footswitch = new Footswitch();
            switchedOn = true;
            footswitch.On = true;
            footswitch.Switched += new FootswitchSwitchedEventHandler(this.FootswitchSwitched);
            footswitch.Location = new System.Drawing.Point(85, 135);
            Controls.Add(footswitch);

            // led
            led = new LED();
            led.On = true;
            led.Location = new System.Drawing.Point(90, 30);
            Controls.Add(led);

            // synchronization
            synchronizer = new FootswitchLedSynchronizer(footswitch, led);
        }


        #endregion

        #region controls_event_handlers
        // handling evetns from controls 

        /// <summary>
        ///  Depth pot event handler.
        /// </summary>
        /// <param name="sender"> Pot that has raised the event </param>
        /// <param name="e"> Event arguments </param>
        public void DepthPotMoved(object sender, PotValueChangedEventArgs e)
        {
            this.depth = e.NewValue;

            EffectSettingsChangedEventArgs settings = new EffectSettingsChangedEventArgs(depthKey, depth);

            if (SettingsChanged != null)
                SettingsChanged(this, settings);
        }

        /// <summary>
        ///  PitchshiftPot ValueChanged event handler.
        /// </summary>
        /// <param name="sender"> Object that has raised the event </param>
        /// <param name="e"> Event arguments </param>
        public void PitchshiftPotMoved(object sender, PotValueChangedEventArgs e)
        {
            pitchShift = e.NewValue;

            EffectSettingsChangedEventArgs changeArgs = new EffectSettingsChangedEventArgs(pitchShiftKey, pitchShift);
            if (SettingsChanged != null)
                SettingsChanged(this, changeArgs);
        }
        
        /// <summary>
        ///  Footswitch.Switched event handler. According to the event arguments signal bypasses or runs through the effect.
        /// </summary>
        /// <param name="sender"> Object that has raised the event. </param>
        /// <param name="args"> Event Arguments </param>
        private void FootswitchSwitched(object sender, FootswitchSwitchedEventArgs args)
        {
            this.switchedOn = args.On;

            // raise the event
            if (this.SettingsChanged != null)
            {
                EffectSettingsChangedEventArgs changeArgs = new EffectSettingsChangedEventArgs(footswitchKey, switchedOn);
                this.SettingsChanged(this, changeArgs);
            }
        }

        #endregion

        #region main_work
        // main method of the pitchshofter

        /// <summary>
        ///  Effects input signal.
        /// </summary>
        /// <param name="indata"> Input signal </param>
        /// <returns> Modified ouput </returns>
        public override double[] effect(double[] indata)
        {
            if (switchedOn)
            {


                double[] ret = new double[dataSize];
                double[] original = new double[dataSize];

                for (int over = 0; over < overlap; over++)
                {
                    // moving buffer
                    Array.Copy(buffer, stepSize, buffer, 0, dataSize - stepSize);
                    Array.Copy(indata, over * stepSize, buffer, dataSize - stepSize, stepSize);

                    // windowing and FFT
                    double[] data = Hann.HannWindow(buffer);
                    fft.Fft(ref data);


                    double frequencyPerBin = sampleRate / (double)dataSize;
                    //double expected = 2*Math.PI*(double)stepSize/(double)frameSize;
                    double expected = (2 * Math.PI) / (double)overlap;

                    for (int i = 0; i <= dataSize / 2; i++)
                    {
                        // analysis
                        double real = data[2 * i];
                        double imag = data[2 * i + 1];

                        // compute magnitude and phase
                        double magn = 2 * Math.Sqrt(real * real + imag * imag);
                        double phase = Math.Atan2(imag, real);

                        // compute phase difference
                        double tmp = phase - lastPhase[i];
                        lastPhase[i] = phase;

                        // subtract expected phase difference
                        tmp -= (double)i * expected;

                        // map delta phase into +/- pi interval 
                        /*long phaseDiffernce = Convert.ToInt64(tmp / Math.PI);
                        if (phaseDiffernce >= 0) 
                            phaseDiffernce += phaseDiffernce&1;
                        else 
                            phaseDiffernce -= phaseDiffernce&1;*/

                        if (tmp > Math.PI)
                            tmp -= 2 * Math.PI;
                        else if (tmp < -Math.PI)
                            tmp += 2 * Math.PI;
                        //tmp -= Math.PI*(double)phaseDiffernce;

                        // get deviation from bin frequency from the +/- PI interval 
                        tmp = overlap * tmp / (2 * Math.PI);

                        // compute the true frequency of k-th partial 
                        tmp = (double)i * frequencyPerBin + tmp * frequencyPerBin;

                        // store magnitude and true frequency in analysis arrays 
                        analysisMagn[i] = magn;
                        analysisFreq[i] = tmp;
                    }


                    // pitch shifting
                    synthesisFreq = new double[dataSize + 1];
                    synthesisMagn = new double[dataSize + 1];

                    for (int i = 0; i <= dataSize / 2; i++)
                    {
                        int index = Convert.ToInt32(i * pitchShift);
                        if (index <= dataSize)
                        {
                            synthesisMagn[index] += analysisMagn[i];
                            synthesisFreq[index] = analysisFreq[i] * pitchShift;
                        }
                    }

                    double[] outData = new double[data.Length];

                    // Synthesis
                    for (int i = 0; i <= dataSize / 2; i++)
                    {
                        // get magnitude and true frequency from synthesis arrays 
                        double magn = synthesisMagn[i];
                        double tmp = synthesisFreq[i];

                        // subtract bin mid frequency
                        tmp -= i * frequencyPerBin;

                        // get bin deviation from freq deviation
                        tmp /= frequencyPerBin;

                        // take osamp into account 
                        tmp = 2 * Math.PI * tmp / overlap;

                        // add the overlap phase advance back  
                        tmp += i * expected;

                        // accumulate delta phase to get bin phase
                        sumPhase[i] += tmp;
                        double phase = sumPhase[i];

                        // get real and imag part and re-interleave 
                        outData[2 * i] = magn * Math.Cos(phase);
                        outData[2 * i + 1] = magn * Math.Sin(phase);
                    }

                    // mixing the data and outdata
                    outData = Mix(outData, data, depth);


                    fft.iFft(ref outData);
                    //return outData;


                    outData = Hann.HannWindow(outData);


                    for (int i = 0; i < dataSize; i++)
                    {
                        outBuffer[i] += 2 * outData[i] / overlap;
                    }

                    // move one step to ret
                    Array.Copy(outBuffer, 0, ret, over * stepSize, stepSize);

                    // copy to orginal - avalilible in next version
                    Array.Copy(buffer, 0, original, over * stepSize, stepSize);

                    // shift outBuffer
                    Array.Copy(outBuffer, stepSize, outBuffer, 0, dataSize - stepSize);
                    for (int i = dataSize - stepSize; i < dataSize; i++)
                        outBuffer[i] = 0;

                }

                return ret;
            }
            else
            {
                Array.Copy(indata, buffer, dataSize);
                Array.Copy(indata, outBuffer, dataSize);
                return indata;

            }

        }

        /// <summary>
        ///  Mixes given data with given data1/data2 mix.
        /// </summary>
        /// <param name="data1"> Signal 1 </param>
        /// <param name="data2"> Signal 2 </param>
        /// <param name="data1depth"> Dry/Wet mix </param>
        /// <returns> Mixed signal </returns>
        protected double[] Mix(double[] data1, double[] data2, double data1depth)
        {
            if (data1.Length != data2.Length)
                throw new ArgumentException("Signal arrays must have same lengths.");

            double[] ret = new double[data1.Length];
            double data2depth = 1 - data1depth;
            for (int i = 0; i < data1.Length; i++)
            {
                ret[i] = data1depth * data1[i] + data2depth * data2[i];
            }

            return ret;
        }

        #endregion

        #region overlap_change
        // changing overlap

        /// <summary>
        ///  Displayed to the user to change overlap
        /// </summary>
        private OverlapFactorForm overlapForm;

        /// <summary>
        ///  Display overlapForm when right button is clicked
        /// </summary>
        /// <param name="e"> Mouse event arguments </param>
        protected override void OnMouseDown(System.Windows.Forms.MouseEventArgs e)
        {
            base.OnMouseDown(e);
            if (e.Button == MouseButtons.Right)
            {
                overlapForm.SetOverlap(this.overlap);
                overlapForm.ShowDialog();
            }
        }

        /// <summary>
        ///  overlapForm event handler. Changes the overlap.
        /// </summary>
        /// <param name="sender"> Object that has raisd the event </param>
        /// <param name="e"> Event arguments </param>
        private void OverlapChanged(object sender, OverlapEventArgs e)
        {
            this.Overlap = e.Overlap;
        }

        #endregion

        #region settings
        // getting and setting the settings of Pitchshifter

        /// <summary>
        ///  Raised when any parameter of effect is changed.
        /// </summary>
        public override event EffectSettingsChangedDelegate SettingsChanged;
        
        /// <summary>
        ///  Gets all current settings of the effect.
        /// </summary>
        /// <returns> All current seetings </returns>
        public override object[] GetAllSettings()
        {
            object[] settings = new object[3];
            settings[0] = this.switchedOn;
            settings[1] = this.pitchShift;
            settings[2] = this.depth;
            return settings;
        }

        /// <summary>
        ///  Sets all settings.
        /// </summary>
        /// <param name="settings"> All settings of the effect. This argument should be output of GetAllSettings method that has been called previously. </param>
        public override void SetAllSettings(object[] settings)
        {
            object[] settingsArray = (object[])settings;

            switchedOn = (bool)settingsArray[0];
            synchronizer.SetValue(switchedOn);

            pitchShift = (double)settingsArray[1];
            pitchShiftPot.SetValue(pitchShift);
            //SetPitchshift(interval);
            //intervalSwitch.State = interval;

            depth = (double)settingsArray[2];
            depthPot.SetValue(depth);
        }

        /// <summary>
        ///  Sets one settings
        /// </summary>
        /// <param name="e"> Setting change to be done </param>
        public override void SetSetting(EffectSettingsChangedEventArgs e)
        {
            if (depthKey == (int)e.Key)
            {
                depth =(double) e.Value;
                depthPot.SetValue(depth);
                return;
            }

            if (pitchShiftKey == (int)e.Key)
            {
                //SetPitchshift((int)e.Value);
                //intervalSwitch.State = (int)e.Value;
                pitchShift = (double)e.Value;
                pitchShiftPot.SetValue(pitchShift);
                return;
            }

            if (footswitchKey == (int)e.Key)
            {
                switchedOn = (bool)e.Value;
                synchronizer.SetValue(switchedOn);
            }
        }

        /// <summary>
        ///  Return array desribing all parameters.
        /// </summary>
        /// <returns> Array describing parameters </returns>
        public override EffectParam[] GetParameters()
        {
            EffectParam[] parameters = new EffectParam[3];

            ListItem[] footswitchState = new ListItem[2];
            footswitchState[0] = new ListItem("Turn on", true);
            footswitchState[1] = new ListItem("Turn off", false);
            parameters[0] = new EffectParam(footswitchKey, "ON/OFF", footswitchState);

            parameters[1] = new EffectParam(pitchShiftKey, "Shift", -1F, 1F);
            parameters[2] = new EffectParam(depthKey, "Depth", 0F, 1F);

            return parameters;
        }

        #endregion
    }

}